#include "edfparser.h"
#include "edfmodel.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sstream>
#include <iostream>

using namespace std;
namespace biosignal
{
namespace edf
{

const char EDFParser::EDF_C[] = "EDF+C";
const char EDFParser::EDF_D[] = "EDF+D";
const char EDFParser::EDF_ANNOTATION[] = "EDF Annotations";
const char EDFParser::TT_CHARS[2] = {20,20};
const char EDFParser::ANN_CHAR_START = 20;
const char EDFParser::ANN_CHAR_END = 20;
const char EDFParser::DURATION_CHAR_START = 21;
const char EDFParser::TAL_END_CHAR = 0;



void trim(string& str)
{
  string::size_type pos = str.find_last_not_of(' ');
  if(pos != string::npos) {
    str.erase(pos + 1);
    pos = str.find_first_not_of(' ');
    if(pos != string::npos) str.erase(0, pos);
  }
  else str.erase(str.begin(), str.end());
}

void read_bytes(istream_ptr in, string& dest, char* buff, int size)
{
    in->get( buff, size);
    dest.assign( buff );
    trim(dest);
}

template <size_t size>
void read_bytes(istream_ptr in, string& dest, char (&buff)[size])
{
    in->get( buff, size);
    dest.assign( buff );
    trim(dest);
}

template <typename T, size_t size>
void read_bytes(istream_ptr in, T& dest, char (&buff)[size])
{
    in->get( buff, size);
    std::stringstream ss ( buff );
    ss >> dest;

}

const EDFModel* EDFParser::parse( const char* file_name )
{
    char buff_2[3];
    char buff_4[5];
    char buff_8[9];
    char buff_16[17];
    char buff_32[33];
    char buff_44[45];
    char buff_80[81];

    EDFModel* edf_model = new EDFModel(file_name);

    read_bytes( edf_model->biosignal_istream, edf_model->m_version, buff_8 );

    read_bytes( edf_model->biosignal_istream, edf_model->m_patient_identification, buff_80 );

    read_bytes( edf_model->biosignal_istream, edf_model->m_recording_identification, buff_80 );

    string start_date;
    //start date recording
    read_bytes( edf_model->biosignal_istream, start_date, buff_8 );

    string start_time;
    //start time recording
    read_bytes( edf_model->biosignal_istream, start_time, buff_8 );

    int day, month, year, hour, minute, second;
    char dot;
    std::stringstream ss(start_date);
    ss >> day;
    ss >> dot;
    ss >> month;
    ss >> dot;
    ss >> year;
    ss >> dot;

    std::stringstream sst(start_time);
    sst >> hour;
    sst >> dot;
    sst >> minute;
    sst >> dot;
    sst >> second;
    sst >> dot;

    // TODO :  parse properly the date
    TimePoint start(year+2000, month, day, hour, minute, second);

    read_bytes( edf_model->biosignal_istream, edf_model->m_bytes_in_header, buff_8);

    //EDF+ support
    read_bytes( edf_model->biosignal_istream, edf_model->m_reserved, buff_44);
    edf_model->m_is_edf_plus = false;
    if (edf_model->m_reserved.find(EDF_C) != string::npos ) {
        edf_model->m_is_edf_plus = true;
        edf_model->m_has_interruptions = false;
    }
    if (edf_model->m_reserved.find(EDF_D) != string::npos ) {
        edf_model->m_is_edf_plus = true;
        edf_model->m_has_interruptions = true;
    }

    read_bytes( edf_model->biosignal_istream, edf_model->m_nr_data_records, buff_8);

    read_bytes( edf_model->biosignal_istream, edf_model->m_data_record_duration, buff_8 );

    TimeOffset duration = TimeOffset::Seconds(edf_model->m_nr_data_records * edf_model->m_data_record_duration);
    edf_model->m_recording_time = TimePeriod(start, duration);
    //number of signals_group
    int nr_signals;
    read_bytes( edf_model->biosignal_istream, nr_signals, buff_4);

    edf_model->_edf_signals.reserve(nr_signals);

    int annotation_signal = -1;
    for(int i=0; i<nr_signals; i++) {
    	string label;
        read_bytes( edf_model->biosignal_istream, label, buff_16);
        EDFSourceSignal* signal = new EDFSourceSignal(edf_model, label);
        if(label.find("EEG")!=string::npos) {
        	EEGSignalType* eeg_type = new EEGSignalType();
        	signal->type( eeg_type );
        	string newlabel = label.substr(4);
        	signal->label( newlabel );
        }

        if( signal->label().find( EDF_ANNOTATION ) != string::npos ) {
            annotation_signal = i;
        }
        edf_model->_edf_signals.push_back(signal);
    }
    for(int i=0; i<nr_signals; i++) {
        read_bytes( edf_model->biosignal_istream, edf_model->_edf_signals[i]->m_transducer_type, buff_80);
    }
    for(int i=0; i<nr_signals; i++) {
        read_bytes( edf_model->biosignal_istream, edf_model->_edf_signals[i]->m_physical_dimension, buff_8);
    }
    for(int i=0; i<nr_signals; i++) {
        read_bytes( edf_model->biosignal_istream, edf_model->_edf_signals[i]->m_physical_minimum, buff_8);
    }
    for(int i=0; i<nr_signals; i++) {
        read_bytes( edf_model->biosignal_istream, edf_model->_edf_signals[i]->m_physical_maximum, buff_8);
    }
    for(int i=0; i<nr_signals; i++) {
        read_bytes( edf_model->biosignal_istream, edf_model->_edf_signals[i]->m_digital_minimum, buff_8);
    }
    for(int i=0; i<nr_signals; i++) {
        read_bytes( edf_model->biosignal_istream, edf_model->_edf_signals[i]->m_digital_maximum, buff_8);
    }
    for(int i=0; i<nr_signals; i++) {
        read_bytes( edf_model->biosignal_istream, edf_model->_edf_signals[i]->m_prefiltering, buff_80);
    }

    edf_model->m_bytes_per_data_record=0;
    // calculation of bytes until annotation in earch data record
    long bytes_skip_until_annotation = 0;
    for(int i=0; i<nr_signals; i++) {
        read_bytes( edf_model->biosignal_istream, edf_model->_edf_signals[i]->m_nr_samples_in_data_record, buff_8);
        if( i < annotation_signal)
            bytes_skip_until_annotation += edf_model->_edf_signals[i]->m_nr_samples_in_data_record*2;

        edf_model->m_bytes_per_data_record+=edf_model->_edf_signals[i]->m_nr_samples_in_data_record*2;
    }
    for(int i=0; i<nr_signals; i++) {
        read_bytes( edf_model->biosignal_istream, edf_model->_edf_signals[i]->m_reserved, buff_32);
        edf_model->_edf_signals[i]->init();
    }

    //only keeping ordinary signals_group
    long annotation_bytes_in_data_record=0;
    if(annotation_signal>=0) {
        annotation_bytes_in_data_record = edf_model->_edf_signals[annotation_signal]->m_nr_samples_in_data_record*2;
        edf_model->_edf_signals.erase(edf_model->_edf_signals.begin()+annotation_signal);
        nr_signals--;
    }
    //timekeeping.edf_model = this;

    fill_time_keeping_and_annotations( edf_model, bytes_skip_until_annotation, annotation_bytes_in_data_record);

	std::list<SourceSignal*> signals;
	std::vector<EDFSourceSignal*>::const_iterator it;
	for(it=edf_model->_edf_signals.begin(); it!=edf_model->_edf_signals.end(); ++it) {
		signals.push_back(*it);
	}
	edf_model->add_signals_info(signals);

    edf_model->open_file_as_binary();;
    return edf_model;
}

// private member functions

void EDFParser::update_remaining_segment( char* annotation_segment, char*& remaining_segment ) {
    int len = strlen(annotation_segment);
    remaining_segment=&annotation_segment[len+1];

    if ( (remaining_segment[1]==TAL_END_CHAR)) {
        remaining_segment=NULL;
    }
}
double EDFParser::parse_time_traking(EDFModel* edf_model, char* annotation_segment, char*& remaining_segment ) {
    int len = strcspn(annotation_segment, TT_CHARS);
    char temp[len];
    strncpy( temp, annotation_segment , len);
    double tt = atof(temp);
    update_remaining_segment( annotation_segment, remaining_segment );
    //cout << len << " : " << temp << "  -  tt : "<< tt << endl; //<< " remaining_segment :" << remaining_segment << endl;
    return tt;
}

/*
    Time-stamped Annotations Lists
    http://www.edfplus.info/specs/edfplus.html#edfplusannotations 2.2.2
*/
void EDFParser::parse_tal(EDFModel* edf_model, char* annotation_segment, char*& remaining_segment ) {
    char* onset;
    char* duration;
    char* annotation;
    if ( strchr(annotation_segment, DURATION_CHAR_START ) != NULL)
    {
        onset = strtok(annotation_segment, &DURATION_CHAR_START);
//        printf ("onset : %s ",onset);
        duration = strtok (NULL, &ANN_CHAR_START);
//        printf ("duration : %s ",duration);
    } else {
        onset = strtok(annotation_segment, &ANN_CHAR_START);
//        printf ("onset : %s ",onset);
    }
    annotation = strtok(NULL, &ANN_CHAR_START);
    char* last_annotation = NULL;
    while(annotation != NULL) {
//        printf ("annotation : %s ",annotation);
        last_annotation = annotation;
        annotation = strtok(NULL, &ANN_CHAR_START);
    }
    if(last_annotation != NULL)
        update_remaining_segment(last_annotation, remaining_segment);

//    cout << endl;
}

void EDFParser::parse_annotation( EDFModel*  edf_model, char* annotation_segment) {
    char* remaining_segment = NULL;
    char* remaining_tal = NULL;
    parse_time_traking(edf_model, annotation_segment, remaining_segment);
    if (remaining_segment != NULL) {
        parse_tal(edf_model, remaining_segment, remaining_tal);
    }

}

void EDFParser::fill_time_keeping_and_annotations( EDFModel*  edf_model, long bytes_skip_until_annotation, long annotation_bytes_in_data_record) {
    if(edf_model->is_edf_plus()) {
        char* annotation_buff = new char[annotation_bytes_in_data_record+1];
        edf_model->biosignal_istream->seekg( edf_model->m_bytes_in_header );
        for(long i=0; i<edf_model->get_nr_dataframes(); i++) {
//        for(long i; i<15; i++) {
            edf_model->biosignal_istream->seekg( bytes_skip_until_annotation, ios_base::cur );
            string annotation_segment;
            //search time keeping mark
            read_bytes(edf_model->biosignal_istream, annotation_segment, annotation_buff, annotation_bytes_in_data_record+1);

            parse_annotation(edf_model, annotation_buff);

        }
    }
}
}
}
